using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

namespace Xacc
{
	/// <summary>
	/// A PropertyGrid for the Web
	/// </summary>
	[ToolboxBitmap(typeof(PropertyGrid))]
	[ToolboxData("<{0}:PropertyGrid runat=server></{0}:PropertyGrid>")]
	public partial class PropertyGrid : CompositeControl
	{

		#region Overrides

		bool xhtmlstrict = true;
		
		protected override void Render(HtmlTextWriter writer)
		{
			if (Visible || (Site != null && Site.DesignMode)) {

				//It seems that there is no such thing as <head/> in VisualStudio designer,
				//	thus putting styles into <body/> directly.
				if (Site != null && Site.DesignMode) {
					writer.Write(
							"<style type='text/css'>@import url('{0}');</style>",
							this.Res("xacc.pg.PropertyGrid.css"));
				}

				bool pad = false;

				if (!(Site != null && Site.DesignMode)) {
					pad = (Page.Request.Browser.Browser == "IE");
				} else {
					pad = true;
				}

				writer.Write(
						@"<div id=""{0}""{1} class=""PG PG_{0}"">",
						ClientID,
						pad ? string.Empty : " style='padding-right:2px'");
				RenderChildren(writer);
				writer.Write("</div>");
			}
		}

		protected override void OnPreRender(EventArgs e)
		{
			if (Site != null && Site.DesignMode) {

			} else {
				if (!Page.ClientScript.IsClientScriptBlockRegistered(
							typeof(PropertyGrid),
							"PropertyGrid_style" + ClientID)) {
				}

				if (xhtmlstrict) {
					var _head = Page.Controls[0] as LiteralControl;

					if (null != _head) {
						//Needed to display correctly in MSIE using current CSS
						_head.Text = _head.Text.Replace("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" >",
						  "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");

						_head.Text = _head.Text.Replace("HEAD", "head");
					}

					registerStylesheet("xacc.pg.PropertyGrid.css");
				}

				if (!this.SupressEmbeddedJQuery) {
					Page.ClientScript.RegisterClientScriptResource(typeof(PropertyGrid), "xacc.js.jquery.pack.js");
					Page.ClientScript.RegisterClientScriptResource(typeof(PropertyGrid), "xacc.js.jquery.debug.js");
				}

				Page.ClientScript.RegisterClientScriptResource(typeof(PropertyGrid), "xacc.pg.PropertyGrid.js");


				if (!Page.ClientScript.IsClientScriptBlockRegistered(
							typeof(PropertyGrid),
							ClientID + "_init")) {
					int lh = Utility.GetFontHeight(this.Font.Name, this.Font.Size) + 5;

					Page.ClientScript.RegisterClientScriptBlock(
							typeof(PropertyGrid),
							ClientID + "_init",
							string.Format(@"
<script type=""text/javascript"">
jQuery(function(){{
	jQuery('#{0}').PropertyGrid({{
		selcolor:	'{1}',
		width:		'{3}',
		bgcolor:	'{4}',
		headerfgcolor:	'{5}',
		lineheight:	{6},
		fgcolor:	'{7}',
		family:		'{8}',
		fontsize:	'{9}'
		}});
}});
</script>", ClientID,
									ColorTranslator.ToHtml(this.SelectedItemNameStyle.BackColor),
									ColorTranslator.ToHtml(this.ItemNameStyle.BackColor),
									this.Width,
									ColorTranslator.ToHtml(this.BackColor),
									ColorTranslator.ToHtml(this.HeaderStyle.BackColor),
									lh,
									ColorTranslator.ToHtml(this.ForeColor),
									this.Font.Name,
									this.Font.Size.Unit.Value));
				}
			}
			
			base.OnPreRender(e);
		}

		void registerStylesheet(string resourceKey)
		{
			HtmlLink _link = new HtmlLink();
			_link.Href = Page.ClientScript.GetWebResourceUrl(
					typeof(PropertyGrid),
					resourceKey);
			_link.Attributes["rel"] = "stylesheet";
			_link.Attributes["text"] = "text/css";
			Page.Header.Controls.Add(_link);
		}
		#endregion

		public PropertyGrid()
			: base()
		{
			this.Font.Name = "Verdana";
			this.Font.Size = FontUnit.Point(8);
			this.BackColor = Color.Gainsboro;
			this.ForeColor = Color.Black;
			this.ItemNameStyle.BackColor = Color.White;
			this.ShowHelp = true;
			this.Width = 300;
		}

		#region Properties

		[DefaultValue(false), Category("Appearance")]
		public bool ShowHelp { get; set; }

		[DefaultValue(false), Category("Behavior")]
		public bool ReadOnly { get; set; }

		[DefaultValue(false), Category("Behavior")]
		public bool SupressEmbeddedJQuery { get; set; }

		#endregion

		#region Object Binding
		object m_selectedObject;

		[Browsable(false)]
		public object SelectedObject {
			get {
				if (null == this.m_selectedObject) {
					var _type = this.ViewState["type"] as string;

					if (!string.IsNullOrEmpty(_type)) {
						this.m_selectedObject = Activator.CreateInstance(Type.GetType(_type));
					}
				}
				return this.m_selectedObject;
			}
			set {
				if (value != this.m_selectedObject) {
					this.ViewState["type"] = value.GetType().AssemblyQualifiedName;
					this.m_selectedObject = value;
					this.DataBind();
				}
			}
		}

		public override void DataBind()
		{
			this.OnDataBinding(EventArgs.Empty);
		}

		protected override void OnDataBinding(EventArgs e)
		{
			base.OnDataBinding(e);
			this.Controls.Clear();
			base.ClearChildState();
			this.CreateControlHierarchy();
			base.ChildControlsCreated = true;
		}

		protected override void CreateChildControls()
		{
			this.Controls.Clear();
			this.CreateControlHierarchy();
			base.ClearChildState();
		}

		protected IEnumerable<PropertyDescriptor> BrowsableObjectProperties {
			get {
				return TypeDescriptor
					.GetProperties(this.SelectedObject)
					.Cast<PropertyDescriptor>()
					.Where(_prop => _prop.IsBrowsable);
			}
		}

		protected IEnumerable<PropertyDescriptor> WritableObjectProperties {
			get {
				return
					this.BrowsableObjectProperties
						.Where(_prop => !_prop.IsReadOnly);
			}
		}

		Control Container;

		protected virtual void CreateControlHierarchy()
		{
			if (null == this.SelectedObject) {
				return;
			}

			this.Controls.Add(new PropertyGridHeader());

			this.Container = new HtmlGenericControl("div") { ID = "cats" };
			this.Controls.Add(this.Container);

			var _propertyGroups =
				from _prop in this.BrowsableObjectProperties
				group _prop by _prop.Category into _group
				orderby _group.Key
				select _group;

			foreach (var _group in _propertyGroups) {
				var pgc = new PropertyGridCategory { CategoryName = _group.Key };
				this.Container.Controls.Add(pgc);

				_group
					.Select(_prop => new PropertyGridItem(_prop))
					.ToList()
					.ForEach(pgc.Controls.Add);
			}

			this.Controls.Add(new PropertyGridFooter { ID = "foot" });
		}

		/// <summary>
		/// Put all property values contained in this control into a given object.
		/// </summary>
		/// <param name="receiver"></param>
		/// <returns></returns>
		public bool MergeTo(object receiver)
		{
			//Presummably, m_selectedObject now contains a stub, that is:
			// a default instance reconstructed by the Activator from a type name,
			// contained in a view state.
			// (Hopefully, "receiver" will be of the same type as m_selectedObject,
			// so the control hierarchy will match.)
			// We need to set it now in order to be able to rebind any object-typed properties
			// after any of their sub-object change.
			// For example, if there's a property oftype(Rectanle),
			// then if either X, Y, Width or Height change,
			// we need to update a whole control for Rectangle,
			// which is normally done by binding to it's SelectedObject, which,
			// in turn is taken from parent control SelectedObject, while
			// the following m_selectedObject being the root of all such selected objects.
			this.m_selectedObject = receiver;
			
			return
				this.Items.ToList()
					.ConvertAll(_ctl => _ctl.MergeTo(receiver))
					.Exists(_merged => _merged);
		}

		#endregion

		IEnumerable<PropertyGridItem> Items {
			get {
				return
					this.Container.Controls.OfType<PropertyGridItem>()
					.Concat(
						from _category in this.Container.Controls.OfType<PropertyGridCategory>()
						from _property in _category.Items
						select _property
					);
			}
		}
	}
}
